package com.evil.application.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.evil.activiti.api.RemoteActivityService;
import com.evil.application.api.RemoteApplicationProcessService;
import com.evil.application.enums.order.OrderSpecialControlEnum;
import com.evil.application.enums.process.CompleteTypeEnum;
import com.evil.application.mapper.ApplicationInfoMapper;
import com.evil.application.mapper.ApplicationProcessMapper;
import com.evil.application.pojo.dto.order.CompleteOrderReqDTO;
import com.evil.application.pojo.dto.process.CopyAppProcessReqDTO;
import com.evil.application.pojo.dto.process.CopyAppProcessRespDTO;
import com.evil.application.pojo.entity.ApplicationInfo;
import com.evil.application.pojo.entity.ApplicationOrder;
import com.evil.application.pojo.entity.ApplicationProcess;
import com.evil.application.service.ApplicationProcessNodeService;
import com.evil.application.service.ApplicationProcessService;
import com.evil.application.util.ApplicationUtil;
import com.evil.common.account.util.LoginUtil;
import com.evil.common.application.dto.ApplicationKeyVersionReqDTO;
import com.evil.common.application.dto.order.data.ControlDataDTO;
import com.evil.common.application.dto.process.*;
import com.evil.common.application.dto.process.node.NodeBaseDTO;
import com.evil.common.application.dto.process.node.NodeRespInfoDTO;
import com.evil.common.application.enums.ApplicationVersionEnum;
import com.evil.common.core.enums.RCodeEnum;
import com.evil.common.core.enums.SwitchEnum;
import com.evil.common.core.exception.BusinessException;
import com.evil.common.core.util.StreamUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 应用流程表
 *
 * @author liyang
 * @date 2022-08-08 00:00
 */
@Slf4j
@RestController
@AllArgsConstructor
public class ApplicationProcessServiceImpl extends ServiceImpl<ApplicationProcessMapper, ApplicationProcess> implements ApplicationProcessService, RemoteApplicationProcessService {

    private final LoginUtil loginUtil;

    private final ApplicationInfoMapper applicationInfoMapper;

    private final ApplicationProcessNodeService applicationProcessNodeService;

    private final RemoteActivityService remoteActivityService;

    private final ApplicationLogServiceImpl logService;

    /**
     * 初始化应用流程
     *
     * @param applicationInfo applicationInfo
     */
    @Override
    public void initProcess(ApplicationInfo applicationInfo) {
        // 先获取（不存在时创建）设计版
        ApplicationVersionEnum designVersion = ApplicationVersionEnum.DESIGN_VERSION;
        ApplicationProcess designProcess = this.baseMapper.isExistByAppIdAndVersion(
                applicationInfo.getApplicationId(), designVersion.getId())
                // 需要初始化节点
                .orElseGet(() -> this.createApplicationProcess(applicationInfo, designVersion, true));

        // 然后获取（不存在时创建）发布版
        ApplicationVersionEnum releaseVersion = ApplicationVersionEnum.RELEASE_VERSION;
        ApplicationProcess releaseProcess = this.baseMapper.isExistByAppIdAndVersion(
                applicationInfo.getApplicationId(), releaseVersion.getId())
                // 不需要初始化节点，发布时会复制
                .orElseGet(() -> this.createApplicationProcess(applicationInfo, releaseVersion, false));

        // 然后发布
        this.replaceProcess(designProcess, releaseProcess, new HashMap<>());
    }

    /**
     * 删除应用流程
     *
     * @param applicationInfo applicationInfo
     */
    @Override
    public void deleteProcess(ApplicationInfo applicationInfo) {
        ApplicationVersionEnum.forEach(em -> {
            ApplicationProcess applicationProcess = this.baseMapper.findByAppIdAndVersion(applicationInfo.getApplicationId(), em.getId());
            applicationProcess.setIsDeleted(SwitchEnum.YES.getId());

            logService.saveAndCheck(this.baseMapper, ApplicationProcess::getProcessId, applicationProcess);

            // 删除流程节点数据
            applicationProcessNodeService.deleteProcessNode(applicationProcess);
        });
    }

    /**
     * 复制应用流程
     *
     * @param copyAppProcessReqDTO copyAppProcessReqDTO
     * @return Map 新旧流程节点id映射
     */
    @Override
    public Map<Integer, Map<Long, Long>> copyProcess(CopyAppProcessReqDTO copyAppProcessReqDTO) {
        Map<Integer, CopyAppProcessRespDTO> copyMap = new LinkedHashMap<>();
        ApplicationVersionEnum.forEach(em -> copyMap.put(em.getId(), this.copyProcess(copyAppProcessReqDTO, em)));

        return StreamUtil.transMapV(copyMap, CopyAppProcessRespDTO::getNodeIdMap);
    }

    /**
     * 发布应用流程
     *
     * @param applicationInfo applicationInfo
     * @param controlIdMap    新旧表单控件id映射
     */
    @Override
    public void publishProcess(ApplicationInfo applicationInfo, Map<Long, Long> controlIdMap) {
        this.replaceProcess(applicationInfo, controlIdMap, true);
    }

    /**
     * 还原应用流程
     *
     * @param applicationInfo applicationInfo
     * @param controlIdMap    新旧表单控件id映射
     */
    @Override
    public void restoreProcess(ApplicationInfo applicationInfo, Map<Long, Long> controlIdMap) {
        this.replaceProcess(applicationInfo, controlIdMap, false);
    }

    /**
     * 发起流程
     *
     * @param applicationOrder applicationOrder
     * @param data             data
     */
    @Override
    public ProcessCreateTaskRespDTO startProcess(ApplicationOrder applicationOrder, List<ControlDataDTO> data) {
        ApplicationProcess applicationProcess = this.baseMapper.findByAppIdAndVersion(
                applicationOrder.getApplicationId(), ApplicationVersionEnum.RELEASE_VERSION.getId());

        if (StringUtils.isBlank(applicationProcess.getProcessKey())) {
            // 应用未发布
            throw new BusinessException(RCodeEnum.APPLICATION_UN_PUSHED);
        }

        // 发起流程
        ProcessCreateTaskReqDTO processCreateTaskReqDTO = new ProcessCreateTaskReqDTO();
        processCreateTaskReqDTO.setProcessKey(applicationProcess.getProcessKey());
        processCreateTaskReqDTO.setData(data);
        processCreateTaskReqDTO.setOrderId(applicationOrder.getOrderId());
        return remoteActivityService.createTask(processCreateTaskReqDTO);
    }

    /**
     * 流转流程
     *
     * @param applicationOrder    applicationOrder
     * @param completeOrderReqDTO completeOrderReqDTO
     */
    @Override
    public void completeProcess(ApplicationOrder applicationOrder, CompleteOrderReqDTO completeOrderReqDTO) {
        // 流转流程
        ProcessCompleteTaskReqDTO processCompleteTaskReqDTO = new ProcessCompleteTaskReqDTO();
        processCompleteTaskReqDTO.setOrderId(applicationOrder.getOrderId());
        processCompleteTaskReqDTO.setProcessInstanceId(applicationOrder.getProcessInstanceId());
        processCompleteTaskReqDTO.setCompleteType(completeOrderReqDTO.getCompleteType());
        processCompleteTaskReqDTO.setData(completeOrderReqDTO.getData());
        processCompleteTaskReqDTO.setRemark(completeOrderReqDTO.getRemark());
        NodeBaseDTO nodeBase = applicationProcessNodeService.findBaseById(completeOrderReqDTO.getNodeId(), false);
        processCompleteTaskReqDTO.setNodeBaseDTO(nodeBase);
        if (CompleteTypeEnum.FALLBACK.getId() == completeOrderReqDTO.getCompleteType()) {
            if (null == completeOrderReqDTO.getFallbackNode()) {
                throw new BusinessException(RCodeEnum.CHOOSE_FALL_BACK_NODE);
            }
            NodeBaseDTO fallbackNodeBase = applicationProcessNodeService.findBaseById(completeOrderReqDTO.getFallbackNode(), false);
            processCompleteTaskReqDTO.setFallbackNode(fallbackNodeBase);

        }
        remoteActivityService.completeTask(processCompleteTaskReqDTO);
    }

    /**
     * 订单转交
     *
     * @param processReassignmentTaskReqDTO processReassignmentTaskReqDTO
     */
    @Override
    public void reassignment(ProcessReassignmentTaskReqDTO processReassignmentTaskReqDTO) {
        remoteActivityService.reassignment(processReassignmentTaskReqDTO);
    }

    /**
     * 应用流程信息
     *
     * @param applicationKeyVersionReqDTO applicationKeyVersionReqDTO
     */
    @Override
    public ApplicationProcessInfoRespDTO applicationProcessInfo(ApplicationKeyVersionReqDTO applicationKeyVersionReqDTO) {
        String applicationKey = applicationKeyVersionReqDTO.getApplicationKey();
        Integer applicationVersion = applicationKeyVersionReqDTO.getApplicationVersion();

        // 获取版本
        ApplicationVersionEnum versionEnum = ApplicationVersionEnum.findById(applicationVersion);

        // 应用信息
        ApplicationInfo applicationInfo = applicationInfoMapper.findByKey(applicationKey);
        // 检查企业
        loginUtil.checkEnterpriseId(applicationInfo.getEnterpriseId());

        // 流程信息
        ApplicationProcess applicationProcess = this.baseMapper.findByAppIdAndVersion(applicationInfo.getApplicationId(), versionEnum.getId());

        // 流程节点
        List<NodeRespInfoDTO> nodes = applicationProcessNodeService.findByProcess(applicationProcess);

        ApplicationProcessInfoRespDTO applicationProcessInfoRespDTO = new ApplicationProcessInfoRespDTO();
        applicationProcessInfoRespDTO.setProcessId(applicationProcess.getProcessId());
        applicationProcessInfoRespDTO.setNode(ApplicationUtil.classifyNode(nodes));
        applicationProcessInfoRespDTO.setPublishStatus(applicationInfo.getPublishStatus());
        return applicationProcessInfoRespDTO;
    }

    /**
     * 替换流程信息
     *
     * @param applicationInfo applicationInfo
     * @param isPublish       isPublish
     */
    private void replaceProcess(ApplicationInfo applicationInfo, Map<Long, Long> controlIdMap, boolean isPublish) {
        // 发布版
        int releaseVersion = ApplicationVersionEnum.RELEASE_VERSION.getId();
        // 设计版
        int designVersion = ApplicationVersionEnum.DESIGN_VERSION.getId();
        ApplicationProcess releaseVersionProcess = this.baseMapper.findByAppIdAndVersion(applicationInfo.getApplicationId(), releaseVersion);
        ApplicationProcess designVersionProcess = this.baseMapper.findByAppIdAndVersion(applicationInfo.getApplicationId(), designVersion);

        if (isPublish) {
            // 发布：设计版 -> 发布版
            this.replaceProcess(designVersionProcess, releaseVersionProcess, controlIdMap);

            // 获取节点信息（发布需要重新部署流程）
            ApplicationKeyVersionReqDTO applicationKeyVersionReqDTO = new ApplicationKeyVersionReqDTO();
            applicationKeyVersionReqDTO.setApplicationKey(applicationInfo.getApplicationKey());
            applicationKeyVersionReqDTO.setApplicationVersion(releaseVersion);
            ApplicationProcessInfoRespDTO applicationProcessInfoRespDTO = this.applicationProcessInfo(applicationKeyVersionReqDTO);

            // 部署流程
            ProcessDeploymentReqDTO processDeploymentReqDTO = BeanUtil.copyProperties(applicationProcessInfoRespDTO, ProcessDeploymentReqDTO.class);
            processDeploymentReqDTO.setDeploymentName(applicationInfo.getApplicationName());
            ProcessDeploymentRespDTO processDeploymentRespDTO = remoteActivityService.processDeployment(processDeploymentReqDTO);

            String processKey = processDeploymentRespDTO.getProcessKey();
            log.info("【发布应用流程】，部署流程，应用key:{}，流程key:{}", applicationInfo.getApplicationKey(), processKey);
            releaseVersionProcess.setProcessKey(processKey);

            logService.saveAndCheck(this.baseMapper, ApplicationProcess::getProcessId, releaseVersionProcess);
        } else {
            // 还原：发布版 -> 设计版
            this.replaceProcess(releaseVersionProcess, designVersionProcess, controlIdMap);
            logService.saveAndCheck(this.baseMapper, ApplicationProcess::getProcessId, designVersionProcess);
        }
    }

    /**
     * 替换流程信息
     *
     * @param source source
     * @param target target
     */
    private void replaceProcess(ApplicationProcess source, ApplicationProcess target, Map<Long, Long> controlIdMap) {
        // 复制应用流程节点
        applicationProcessNodeService.copyNode(source, target, controlIdMap, true);

        // 这里预留以后流程会有附加表需要复制
    }

    /**
     * 复制应用流程
     *
     * @param copyAppProcessReqDTO copyAppProcessReqDTO
     * @param em                   em
     * @return CopyAppProcessRespDTO
     */
    private CopyAppProcessRespDTO copyProcess(CopyAppProcessReqDTO copyAppProcessReqDTO, ApplicationVersionEnum em) {
        ApplicationInfo source = copyAppProcessReqDTO.getSource();
        ApplicationInfo target = copyAppProcessReqDTO.getTarget();
        Map<Integer, Map<Long, Long>> versionControlIdMap = copyAppProcessReqDTO.getVersionControlIdMap();

        if (!versionControlIdMap.containsKey(em.getId())) {
            throw new BusinessException(RCodeEnum.ERROR_RUNTIME);
        }
        // 版本合并（复制流程中，部分子功能直接用发布版控件）
        Map<Long, Long> controlIdMap = new HashMap<>();
        versionControlIdMap.forEach((versionType, map) -> controlIdMap.putAll(map));
        controlIdMap.putAll(StreamUtil.toMap(OrderSpecialControlEnum.getSpecialControlIds()));

        ApplicationProcess applicationProcess = this.baseMapper.findByAppIdAndVersion(source.getApplicationId(), em.getId());

        ApplicationProcess copyProcess = BeanUtil.copyProperties(applicationProcess, ApplicationProcess.class);
        copyProcess.setProcessId(null);
        copyProcess.setApplicationId(target.getApplicationId());
        copyProcess.setProcessKey(null);

        logService.saveAndCheck(this.baseMapper, ApplicationProcess::getProcessId, copyProcess);

        // 复制流程节点
        Map<Long, Long> nodeIdMap = applicationProcessNodeService.copyNode(applicationProcess, copyProcess, controlIdMap, false);

        return new CopyAppProcessRespDTO(copyProcess, nodeIdMap);
    }

    private ApplicationProcess createApplicationProcess(ApplicationInfo applicationInfo, ApplicationVersionEnum versionEnum, boolean initNode) {
        ApplicationProcess process = new ApplicationProcess();
        process.setEnterpriseId(applicationInfo.getEnterpriseId());
        process.setApplicationId(applicationInfo.getApplicationId());
        process.setApplicationVersion(versionEnum.getId());
        process.setIsDeleted(SwitchEnum.NO.getId());

        logService.saveAndCheck(this.baseMapper, ApplicationProcess::getProcessId, process);

        // 初始化流程节点
        if (initNode) {
            applicationProcessNodeService.initNode(process);
        }

        return process;
    }
}
